﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BrightIdeasSoftware;
using MoneyWentWhere.DataModel;
using System.Text.RegularExpressions;

namespace MoneyWentWhere.WinFormsUI.UI
{
    class CompositeFilter : IModelFilter
    {
        List<IModelFilter> _require = new List<IModelFilter>();
        List<IModelFilter> _exclude = new List<IModelFilter>();
        List<IModelFilter> _include = new List<IModelFilter>();


        public List<IModelFilter> Require { get { return _require; } }
        public List<IModelFilter> Exclude { get { return _exclude; } }
        public List<IModelFilter> Include { get { return _include; } }

        public bool Filter(object modelObject)
        {
            // Exclude: reject on ANY match
            foreach (IModelFilter excludeFilter in Exclude)
            {
                if (excludeFilter.Filter(modelObject)) { return false; }
            }

            // Require: ALL must match match
            foreach (IModelFilter requireFilter in Require)
            {
                if (!requireFilter.Filter(modelObject)) { return false; }
            }


            // Include: if there are no filters, accept the item
            if (Include.Count == 0) { return true; }

            // Include: if there are filters, ANY must match. 
            foreach (IModelFilter includeFilter in Include)
            {
                if (includeFilter.Filter(modelObject)) { return true; }
            }
            return false;
        }

        public override bool Equals(object obj)
        {
            CompositeFilter that = obj as CompositeFilter;
            if (that == null) { return false; }

            return Enumerable.SequenceEqual(Include, that.Include) &&
                Enumerable.SequenceEqual(Exclude, that.Exclude);

        }

        public static CompositeFilter Create(String s, ObjectListView olv)
        {
            CompositeFilter rf = new CompositeFilter();
            if (String.IsNullOrEmpty(s)) { return rf; }

            String[] parts = s.Split(' ');
            foreach (String part in parts)
            {
                String filterRep = part;

                if (part.StartsWith("-") || part.StartsWith("?") || part.StartsWith("+"))
                {
                    filterRep = part.Substring(1);
                }

                IModelFilter subFilter = ParseFilter(filterRep, olv);

                if (part.StartsWith("-"))
                {
                    rf.Exclude.Add(subFilter);
                }
                else if (part.StartsWith("?"))
                {
                    rf.Include.Add(subFilter);
                }
                else
                {
                    rf.Require.Add(subFilter);
                }
            }

            return rf;
        }

        static IModelFilter ParseFilter(String s, ObjectListView olv)
        {
            IModelFilter filter;

            if (TransactionKindFilter.TryParse(s, out filter))
            {
                return filter;
            }
            if (DcAmountRangeFilter.TryParse(s, out filter))
            {
                return filter;
            }

            // Default: text match
            filter = new TextMatchFilter(olv, s, StringComparison.InvariantCultureIgnoreCase);
            return filter;
        }
    }

    class TransactionKindFilter : IModelFilter
    {
        public TransactionKind Kind;

        public static bool TryParse(String s, out IModelFilter filter)
        {
            TransactionKindFilter tkFilter = new TransactionKindFilter();
            filter = tkFilter;

            if (s.StartsWith("k:", StringComparison.InvariantCultureIgnoreCase))
            {
                s = s.Substring(2);
                return TryParseKind(s, out tkFilter.Kind);
            }

            return false;
        }

        static bool TryParseKind(String s, out TransactionKind kind)
        {
            kind = TransactionKind.Normal;
            if (s.StartsWith("n", StringComparison.InvariantCultureIgnoreCase)) { kind = TransactionKind.Normal; return true; }
            if (s.StartsWith("t", StringComparison.InvariantCultureIgnoreCase)) { kind = TransactionKind.Transfer; return true; }
            if (s.StartsWith("r", StringComparison.InvariantCultureIgnoreCase)) { kind = TransactionKind.Reconcile; return true; }
            return false;
        }

        public bool Filter(object modelObject)
        {
            Transaction tx = modelObject as Transaction;
            if (tx == null) { return false; }

            return tx.Kind == Kind;
        }
    }

    class DateFilter : IModelFilter
    {
        readonly DateTime Start;
        readonly DateTime End;
    
        public DateFilter(DateTime start, DateTime end)
        {
            Start = start;
            End = end;
        }

        public bool Filter(object modelObject)
        {
            Transaction tx = modelObject as Transaction;
            if (tx == null) { return false; }

            return Start <= tx.Date && tx.Date <= End;
        }
    }

    class DcAmountRangeFilter : IModelFilter
    {
        decimal Min = decimal.MinValue;
        bool MinInclusive = false;
        decimal Max = decimal.MaxValue;
        bool MaxInclusive = false;

        public static bool TryParse(String s, out IModelFilter filter)
        {
            DcAmountRangeFilter range = new DcAmountRangeFilter();
            filter = range;

            s = s.Trim();

            // <x <=x >x >=x format
            if (s.StartsWith("<"))
            {
                s = s.Substring(1);
                if (s.StartsWith("="))
                {
                    range.MaxInclusive = true;
                    s = s.Substring(1);
                }

                return decimal.TryParse(s, out range.Max);
            }
            else if (s.StartsWith(">"))
            {
                s = s.Substring(1);
                if (s.StartsWith("="))
                {
                    range.MinInclusive = true;
                    s = s.Substring(1);
                }

                return decimal.TryParse(s, out range.Min);
            }

            // (x-y) or [x-y] format
            Match m = Regex.Match(s, @"(\[|\()([\|\d\,\.]+)-([\|\d\,\.]+)(\]|\))");
            if (m.Groups.Count != 5) { return false; }

            if (m.Groups[1].Value == "[") { range.MinInclusive = true; }
            if (!decimal.TryParse(m.Groups[2].Value, out range.Min)) { return false; }
            if (!decimal.TryParse(m.Groups[3].Value, out range.Max)) { return false; }
            if (m.Groups[4].Value == "]") { range.MaxInclusive = true; }

            return true;
        }

        public bool Filter(object modelObject)
        {
            Transaction tx = modelObject as Transaction;
            if (tx == null) { return false; }

            return WithinRange(tx.DcAmount);
        }

        public bool WithinRange(decimal amount)
        {
            if (MinInclusive && amount == Min) { return true; }
            if (MaxInclusive && amount == Max) { return true; }
            return Min < amount && amount < Max;
        }
    }
}
